/*
 * Copyright (C) 2010 Fores Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.fores.midori.server.plugin;

import java.io.Serializable;

/**
 * メソッド名と引数の数を保持するクラス。<br>
 * (Immutableオブジェクト)<br>
 */
public class MethodNameAndArgsLength implements Serializable {
	//==========================================================
	//定数

	/**
	 * シリアルバージョンID
	 * (少しでも処理が軽くなるようにするために定義しておく)
	 */
	private static final long serialVersionUID = 1L;


	//==========================================================
	//フィールド

	/**
	 * メソッド名
	 */
	public final String methodName;

	/**
	 * 引数の数
	 */
	public final int argsLength;

	/**
	 * toString()の結果をキャッシュしておくための変数
	 */
	//(あくまでもキャッシュのため永続化対象に含める必要はないので、transientをつけている)
	private transient String toStringCache = null;

	/**
	 * hashCode()の結果をキャッシュしておくための変数
	 */
	//(あくまでもキャッシュのため永続化対象に含める必要はないので、transientをつけている)
	//(intではなくIntegerにしているのは、無効な値をnullで表現したいため)
	private transient Integer hashCodeCache = null;


	//==========================================================
	//メソッド

	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//コンストラクタ

	/**
	 * コンストラクタです。<br>
	 *
	 * @param methodName メソッド名
	 * @param argsLength 引数の数
	 * @throws IllegalArgumentException メソッド名にnullまたは空文字列が指定された場合, 引数の数に負の値が指定された場合
	 */
	public MethodNameAndArgsLength(String methodName, int argsLength)
			throws IllegalArgumentException {
		//==========================================================
		//引数のチェック

		//メソッド名にnullが指定された場合
		if (methodName == null) {
			//IllegalArgumentExceptionを投げる
			throw new IllegalArgumentException("メソッド名にnullを指定することはできません。");
		}

		//メソッド名に空文字列が指定された場合
		if ("".equals(methodName)) {
			//IllegalArgumentExceptionを投げる
			throw new IllegalArgumentException("メソッド名に空文字列を指定することはできません。");
		}

		//引数の数に負の値が指定された場合
		if (argsLength < 0) {
			//IllegalArgumentExceptionを投げる
			throw new IllegalArgumentException("引数の数に負の値を指定することはできません。");
		}


		//==========================================================
		//引数の値をフィールドに設定

		//メソッド名
		this.methodName = methodName;

		//引数の数
		this.argsLength = argsLength;
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//オーバーライドするメソッド

	/**
	 * オブジェクトの文字列表現を返します。<br>
	 *
	 * @return オブジェクトの文字列表現
	 */
	@Override
	public String toString() {
		//フィールドのtoString()の結果をキャッシュしておくための変数の値をローカル変数として取得
		String cache = this.toStringCache;

		//キャッシュから値を取得できなかった場合
		if (cache == null) {
			//フィールドの内容から文字列表現を作成する
			cache = "{methodName=" + this.methodName + ", argsLength="
					+ this.argsLength + "}";

			//作成した文字列表現をフィールドのキャッシュに設定する
			//(synchronizedで同期をとっていないので、複数スレッドで同時にこの処理を行った場合、
			// キャッシュに代入されるオブジェクトがスレッド毎に異なる可能性があるが、
			// メインのフィールドはImmutableなので、文字列表現も毎回同じ内容になるので問題ない)
			this.toStringCache = cache;
		}

		//フィールドのキャッシュから取得、または生成した値を返す
		return cache;
	}

	/**
	 * オブジェクトのハッシュコード値を返します。<br>
	 *
	 * @return オブジェクトのオブジェクトのハッシュコード値
	 */
	@Override
	public int hashCode() {
		//フィールドのtoString()の結果をキャッシュしておくための変数の値をローカル変数として取得
		Integer cache = this.hashCodeCache;

		//キャッシュから値を取得できなかった場合
		if (cache == null) {
			//フィールドの内容からハッシュコードを作成する
			//(Commons LangのHashCodeBuilderと同じ仕組みを使う)
			//(17や37という数字は、HashCodeBuilderのデフォルト値に使われている適当な素数の値)
			int iTotal = 17;
			int iConstant = 37;
			iTotal = iTotal * iConstant + this.methodName.hashCode();
			iTotal = iTotal * iConstant + this.argsLength;
			cache = iTotal;

			//作成したハッシュコードをフィールドのキャッシュに設定する
			//(synchronizedで同期をとっていないので、複数スレッドで同時にこの処理を行った場合、
			// キャッシュに代入されるオブジェクトがスレッド毎に異なる可能性があるが、
			// メインのフィールドはImmutableなので、ハッシュコードも毎回同じ値になるので問題ない)
			this.hashCodeCache = cache;
		}

		//フィールドのキャッシュから取得、または生成した値を返す
		return cache;
	}

	/**
	 * このオブジェクトが指定されたオブジェクトと等しいかどうかを判定します。<br>
	 *
	 * @param obj 比較対象のオブジェクト
	 * @return 2つのオブジェクトが同じと見なす場合は true、そうでない場合は false
	 */
	@Override
	public boolean equals(Object obj) {
		//比較対象のオブジェクトがnullでなく、同じクラスのインスタンスの場合
		if ((obj != null) && (obj instanceof MethodNameAndArgsLength)) {
			//比較対象のオブジェクトを元の型にキャスト
			MethodNameAndArgsLength otherObj = (MethodNameAndArgsLength) obj;

			//メソッド名と引数の数が両方共一致する場合
			if (this.methodName.equals(otherObj.methodName)
					&& (this.argsLength == otherObj.argsLength)) {
				//同じオブジェクトと見なすのでtrueを返す
				return true;
			}
		}

		//違うオブジェクトと見なすのでfalseを返す
		return false;
	}

}
